package interpreter

import (
	"io"
	"fmt"
	"kumachan/standalone/rx"
	"kumachan/standalone/util"
	"kumachan/standalone/util/richtext"
	"kumachan/interpreter/lang/ast"
	"kumachan/interpreter/lang/textual/syntax"
	"kumachan/interpreter/lang/textual/parser"
	"kumachan/interpreter/lang/textual/transformer"
	"kumachan/interpreter/compiler/checker"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
	"kumachan/interpreter/compiler/generator"
	"kumachan/interpreter/runtime/def"
	"kumachan/interpreter/runtime/vm"
	"kumachan/interpreter/runtime/lib/debug"
)


type ReplContext struct {
	Program         *checker.Program
	MachinePointer  **vm.Machine
}

type ReplConfig struct {
	Input   io.Reader
	Output  io.Writer
}

func repl(ctx ReplContext, cfg ReplConfig) rx.Observable {
	var program = ctx.Program
	var machine_ptr = ctx.MachinePointer
	var input = cfg.Input
	var output = cfg.Output
	return rx.NewGoroutineSingle(func(ctx *rx.Context) (rx.Object, bool) {
		var m = *machine_ptr
		var sched = m.GetScheduler()
		var bindings = make(map[string] *checked.LocalBinding)
		var values = make(map[string] def.Value)
		var bind = func(name string, expr *checked.Expr, value def.Value) {
			bindings[name] = &checked.LocalBinding {
				Name:     name,
				Type:     expr.Type,
				Location: expr.Info.Location,
			}
			values[name] = value
		}
		const repl_title = "** REPL **"
		fmt.Fprintf(output, "%s\n\n", repl_title)
		var cmd_id = uint(0)
		for {
			cmd_id += 1
			var code, ok = replReadLine(input, output, cmd_id)
			if !(ok) {
				// EOF
				return nil, true
			}
			if len(code) == 0 {
				cmd_id -= 1
				continue
			}
			var cmd_label_err = fmt.Sprintf("\033[31m(%d)\033[0m", cmd_id)
			var cmd_ast_name = fmt.Sprintf("[%d]", cmd_id)
			const repl_root = syntax.ReplRootPartName
			cst, p_err := parser.Parse(code, repl_root, cmd_ast_name)
			if p_err != nil {
				fmt.Fprintf(output,
					"[%d] error:\n%s\n", cmd_id, replText(p_err.Message()))
				continue
			}
			var cmd = transformer.Transform(cst).(ast.ReplRoot)
			var expr_node = ast.ReplCmdGetExpr(cmd.Cmd)
			var expr, err = checker.TypeCheckStandaloneExpr (
				nil, expr_node, program, bindings,
			)
			if err != nil {
				fmt.Fprintf(output,
					"[%d] error:\n%s\n", cmd_id, replText(err.Message()))
				continue
			} else {
				goto ok
			}
			ok:
			var temp_name = fmt.Sprintf("temp%d", cmd_id)
			var info = def.FunctionInfo {
				Symbol:   def.MakeSymbol("", temp_name),
				Location: expr.Info.Location,
			}
			var seed, captures = generator.CompileStandaloneExpr(expr, info)
			var f = m.EvaluateStandaloneFunctionSeed(seed)
			var entity = (*f).(def.UsualFunction).Entity
			var inject = make([] def.Value, len(captures))
			for i, item := range captures {
				var item_value, exists = values[item.Name]
				if !(exists) { panic("something went wrong") }
				inject[i] = item_value
			}
			var closure = def.ValFuncUsual(def.UsualFunction {
				Entity:  entity,
				Context: inject,
			})
			var value = m.Call(ctx, closure, nil)
			bind(temp_name, expr, value)
			replInspect(value, expr.Type, output, cmd_id)
			switch cmd := cmd.Cmd.(type) {
			case ast.ReplAssign:
				var specified_name = string(cmd.Name.Name)
				bind(specified_name, expr, value)
			case ast.ReplDo:
				var action, ok = value.(rx.Observable)
				if ok {
					var v_type, e_type = replMatchObservable(expr.Type, program)
					replDo(action, sched, output, v_type, e_type, cmd_id)
				} else {
					fmt.Fprintf(output,
						"%s failure:\nvalue is not an effect\n", cmd_label_err)
				}
			case ast.ReplEval:
				// do nothing extra
			}
		}
	})
}

func replText(msg richtext.Block) string {
	return msg.RenderConsole(richtext.RenderOptionsConsole {
		AnsiPalette: richtext.DefaultPalette(),
	})
}

func replReadLine(input io.Reader, output io.Writer, cmd_id uint) (([] rune), bool) {
	{ var _, err = fmt.Fprintf(output, "\033[1m[%d]\033[0m ", cmd_id)
	if err != nil { panic(err) } }
	var code, err = util.WellBehavedReadLine(input)
	if err != nil {
		if err == io.EOF {
			fmt.Fprintf(output, "\n")
			return nil, false
		} else {
			panic(err)
		}
	}
	return code, true
}

func replInspect(value def.Value, t typsys.Type, output io.Writer, cmd_id uint) {
	var cmd_label = fmt.Sprintf("\033[34m(%d)\033[0m", cmd_id)
	var msg = debug.Inspect(value, t)
	var text = msg.RenderConsole(richtext.RenderOptionsConsole {
		AnsiPalette: richtext.DefaultPalette(),
	})
	fmt.Fprintf(output,
		"%s\n%s\n",
		cmd_label, text)
}

func replMatchObservable(t typsys.Type, p *checker.Program) (typsys.Type, typsys.Type) {
	var v_type, e_type, _ = checker.MatchObservable(t, p.Types, p.ModName)
	return v_type, e_type
}

func replDo (
	action  rx.Observable,
	sched   rx.Scheduler,
	output  io.Writer,
	v_type  typsys.Type,
	e_type  typsys.Type,
	cmd_id  uint,
) {
	var cmd_label_ok = fmt.Sprintf("\033[32m(%d)\033[0m", cmd_id)
	var cmd_label_err = fmt.Sprintf("\033[31m(%d)\033[0m", cmd_id)
	var ch_values = make(chan rx.Object, 1024)
	var ch_error = make(chan rx.Object, 4)
	var receiver = rx.Receiver {
		Context:   rx.Background(),
		Values:    ch_values,
		Error:     ch_error,
	}
	rx.Schedule(action, sched, receiver)
	for {
		select {
		case v, not_closed := <- ch_values:
			if not_closed {
				var msg = debug.Inspect(v, v_type)
				_, err := fmt.Fprintf(output,
					"%s <value>\n%s\n", cmd_label_ok, replText(msg))
				if err != nil { panic(err) }
			} else {
				_, err := fmt.Fprintf(output,
					"%s <terminated> completed\n\n", cmd_label_ok)
				if err != nil { panic(err) }
				return
			}
		case e, not_closed := <- ch_error:
			if not_closed {
				var msg = debug.Inspect(e, e_type)
				_, err := fmt.Fprintf(output,
					"%s <exception>\n%s\n", cmd_label_err, replText(msg))
				if err != nil { panic(err) }
			} else {
				_, err := fmt.Fprintf(output,
					"%s <terminated> exception occurred\n\n", cmd_label_err)
				if err != nil { panic(err) }
				return
			}
		}
	}
	return
}


